#!/usr/bin/env node
/**
 * TypeScript Compiler API Bridge
 * 
 * Node.js bridge to TypeScript Compiler API for Python integration.
 * Provides TypeScript AST parsing, type checking, and module resolution.
 */

const ts = require('typescript');
const fs = require('fs');
const path = require('path');

class TypeScriptBridge {
    constructor() {
        this.program = null;
        this.checker = null;
        this.compilerOptions = {
            target: ts.ScriptTarget.ES2020,
            module: ts.ModuleKind.CommonJS,
            moduleResolution: ts.ModuleResolutionKind.NodeJs,
            allowJs: true,
            allowSyntheticDefaultImports: true,
            esModuleInterop: true,
            forceConsistentCasingInFileNames: true,
            strict: false,
            skipLibCheck: true,
            declaration: false,
            declarationMap: false,
            sourceMap: false,
            noEmit: true
        };
    }

    /**
     * Initialize TypeScript program with files and options
     */
    initialize(filePaths, tsConfigPath = null) {
        try {
            if (tsConfigPath && fs.existsSync(tsConfigPath)) {
                const configFile = ts.readConfigFile(tsConfigPath, ts.sys.readFile);
                if (configFile.error) {
                    return { success: false, error: `Error reading tsconfig: ${configFile.error.messageText}` };
                }
                
                const parsedConfig = ts.parseJsonConfigFileContent(
                    configFile.config,
                    ts.sys,
                    path.dirname(tsConfigPath)
                );
                
                this.compilerOptions = { ...this.compilerOptions, ...parsedConfig.options };
            }

            this.program = ts.createProgram(filePaths, this.compilerOptions);
            this.checker = this.program.getTypeChecker();

            return { success: true };
        } catch (error) {
            return { success: false, error: error.message };
        }
    }

    /**
     * Parse a single file and return AST information
     */
    parseFile(filePath) {
        try {
            if (!fs.existsSync(filePath)) {
                return { success: false, error: `File not found: ${filePath}` };
            }

            const sourceFile = this.program ? 
                this.program.getSourceFile(filePath) : 
                ts.createSourceFile(
                    path.basename(filePath),
                    fs.readFileSync(filePath, 'utf8'),
                    ts.ScriptTarget.Latest,
                    true
                );

            if (!sourceFile) {
                return { success: false, error: `Failed to parse file: ${filePath}` };
            }

            const result = {
                success: true,
                fileName: sourceFile.fileName,
                languageVersion: sourceFile.languageVersion,
                isDeclarationFile: sourceFile.isDeclarationFile,
                hasNoDefaultLib: sourceFile.hasNoDefaultLib,
                functions: [],
                classes: [],
                interfaces: [],
                types: [],
                imports: [],
                exports: [],
                variables: [],
                callExpressions: [],
                methodCalls: [],
                propertyAccess: []
            };

            // Walk the AST and extract information
            this.walkNode(sourceFile, result, 0);

            return result;
        } catch (error) {
            return { success: false, error: error.message };
        }
    }

    /**
     * Walk AST node and extract relevant information
     */
    walkNode(node, result, depth = 0) {
        try {
            switch (node.kind) {
                case ts.SyntaxKind.FunctionDeclaration:
                    this.extractFunction(node, result);
                    break;
                case ts.SyntaxKind.MethodDeclaration:
                    this.extractMethod(node, result);
                    break;
                case ts.SyntaxKind.ArrowFunction:
                case ts.SyntaxKind.FunctionExpression:
                    this.extractAnonymousFunction(node, result);
                    break;
                case ts.SyntaxKind.ClassDeclaration:
                    this.extractClass(node, result);
                    break;
                case ts.SyntaxKind.InterfaceDeclaration:
                    this.extractInterface(node, result);
                    break;
                case ts.SyntaxKind.TypeAliasDeclaration:
                    this.extractTypeAlias(node, result);
                    break;
                case ts.SyntaxKind.ImportDeclaration:
                    this.extractImport(node, result);
                    break;
                case ts.SyntaxKind.ExportDeclaration:
                case ts.SyntaxKind.ExportAssignment:
                    this.extractExport(node, result);
                    break;
                case ts.SyntaxKind.VariableDeclaration:
                    this.extractVariable(node, result);
                    break;
                case ts.SyntaxKind.CallExpression:
                    this.extractCallExpression(node, result);
                    break;
                case ts.SyntaxKind.PropertyAccessExpression:
                    this.extractPropertyAccess(node, result);
                    break;
            }

            // Recursively visit children (limit depth to prevent infinite recursion)
            if (depth < 50) {
                ts.forEachChild(node, child => this.walkNode(child, result, depth + 1));
            }
        } catch (error) {
            // Continue processing even if individual node fails
            console.error(`Error processing node at depth ${depth}:`, error.message);
        }
    }

    /**
     * Extract function declaration information
     */
    extractFunction(node, result) {
        const func = {
            name: node.name ? node.name.text : '<anonymous>',
            kind: 'function',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: [],
            returnType: null,
            modifiers: [],
            isAsync: this.hasModifier(node, ts.SyntaxKind.AsyncKeyword),
            isExported: this.isExported(node),
            typeParameters: [],
            signature: this.getNodeText(node),
            documentation: this.getJSDocComment(node)
        };

        // Extract parameters
        if (node.parameters) {
            func.parameters = node.parameters.map(param => ({
                name: param.name.text,
                type: param.type ? this.getTypeText(param.type) : 'any',
                optional: !!param.questionToken,
                defaultValue: param.initializer ? this.getNodeText(param.initializer) : null
            }));
        }

        // Extract return type
        if (node.type) {
            func.returnType = this.getTypeText(node.type);
        }

        // Extract modifiers
        if (node.modifiers) {
            func.modifiers = node.modifiers.map(mod => ts.SyntaxKind[mod.kind]);
        }

        // Extract type parameters
        if (node.typeParameters) {
            func.typeParameters = node.typeParameters.map(tp => ({
                name: tp.name.text,
                constraint: tp.constraint ? this.getTypeText(tp.constraint) : null,
                default: tp.default ? this.getTypeText(tp.default) : null
            }));
        }

        result.functions.push(func);
    }

    /**
     * Extract method declaration information
     */
    extractMethod(node, result) {
        const method = {
            name: node.name ? this.getPropertyName(node.name) : '<anonymous>',
            kind: 'method',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: [],
            returnType: null,
            modifiers: [],
            isAsync: this.hasModifier(node, ts.SyntaxKind.AsyncKeyword),
            isStatic: this.hasModifier(node, ts.SyntaxKind.StaticKeyword),
            isPrivate: this.hasModifier(node, ts.SyntaxKind.PrivateKeyword),
            isProtected: this.hasModifier(node, ts.SyntaxKind.ProtectedKeyword),
            isPublic: this.hasModifier(node, ts.SyntaxKind.PublicKeyword),
            isAbstract: this.hasModifier(node, ts.SyntaxKind.AbstractKeyword),
            signature: this.getNodeText(node),
            documentation: this.getJSDocComment(node)
        };

        // Extract parameters (same as function)
        if (node.parameters) {
            method.parameters = node.parameters.map(param => ({
                name: param.name.text,
                type: param.type ? this.getTypeText(param.type) : 'any',
                optional: !!param.questionToken,
                defaultValue: param.initializer ? this.getNodeText(param.initializer) : null
            }));
        }

        if (node.type) {
            method.returnType = this.getTypeText(node.type);
        }

        result.functions.push(method);
    }

    /**
     * Extract anonymous function information
     */
    extractAnonymousFunction(node, result) {
        const func = {
            name: '<anonymous>',
            kind: node.kind === ts.SyntaxKind.ArrowFunction ? 'arrow_function' : 'function_expression',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: [],
            returnType: null,
            isAsync: this.hasModifier(node, ts.SyntaxKind.AsyncKeyword),
            signature: this.getNodeText(node).substring(0, 100) + '...'
        };

        if (node.parameters) {
            func.parameters = node.parameters.map(param => ({
                name: param.name.text,
                type: param.type ? this.getTypeText(param.type) : 'any',
                optional: !!param.questionToken
            }));
        }

        result.functions.push(func);
    }

    /**
     * Extract class declaration information
     */
    extractClass(node, result) {
        const cls = {
            name: node.name ? node.name.text : '<anonymous>',
            kind: 'class',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            methods: [],
            properties: [],
            constructors: [],
            extends: null,
            implements: [],
            modifiers: [],
            isExported: this.isExported(node),
            isAbstract: this.hasModifier(node, ts.SyntaxKind.AbstractKeyword),
            typeParameters: [],
            documentation: this.getJSDocComment(node)
        };

        // Extract heritage clauses (extends, implements)
        if (node.heritageClauses) {
            for (const heritage of node.heritageClauses) {
                if (heritage.token === ts.SyntaxKind.ExtendsKeyword) {
                    cls.extends = heritage.types.map(t => this.getNodeText(t));
                } else if (heritage.token === ts.SyntaxKind.ImplementsKeyword) {
                    cls.implements = heritage.types.map(t => this.getNodeText(t));
                }
            }
        }

        // Extract type parameters
        if (node.typeParameters) {
            cls.typeParameters = node.typeParameters.map(tp => ({
                name: tp.name.text,
                constraint: tp.constraint ? this.getTypeText(tp.constraint) : null
            }));
        }

        // Extract members
        if (node.members) {
            for (const member of node.members) {
                if (member.kind === ts.SyntaxKind.MethodDeclaration) {
                    cls.methods.push(this.extractMethodForClass(member));
                } else if (member.kind === ts.SyntaxKind.PropertyDeclaration) {
                    cls.properties.push(this.extractProperty(member));
                } else if (member.kind === ts.SyntaxKind.Constructor) {
                    cls.constructors.push(this.extractConstructor(member));
                }
            }
        }

        result.classes.push(cls);
    }

    /**
     * Extract interface declaration information
     */
    extractInterface(node, result) {
        const iface = {
            name: node.name.text,
            kind: 'interface',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            properties: [],
            methods: [],
            extends: [],
            typeParameters: [],
            isExported: this.isExported(node),
            documentation: this.getJSDocComment(node)
        };

        // Extract heritage clauses
        if (node.heritageClauses) {
            for (const heritage of node.heritageClauses) {
                if (heritage.token === ts.SyntaxKind.ExtendsKeyword) {
                    iface.extends = heritage.types.map(t => this.getNodeText(t));
                }
            }
        }

        // Extract type parameters
        if (node.typeParameters) {
            iface.typeParameters = node.typeParameters.map(tp => ({
                name: tp.name.text,
                constraint: tp.constraint ? this.getTypeText(tp.constraint) : null
            }));
        }

        // Extract members
        if (node.members) {
            for (const member of node.members) {
                if (member.kind === ts.SyntaxKind.PropertySignature) {
                    iface.properties.push(this.extractPropertySignature(member));
                } else if (member.kind === ts.SyntaxKind.MethodSignature) {
                    iface.methods.push(this.extractMethodSignature(member));
                }
            }
        }

        result.interfaces.push(iface);
    }

    /**
     * Extract type alias declaration
     */
    extractTypeAlias(node, result) {
        const typeAlias = {
            name: node.name.text,
            kind: 'type_alias',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            type: this.getTypeText(node.type),
            typeParameters: [],
            isExported: this.isExported(node),
            documentation: this.getJSDocComment(node)
        };

        if (node.typeParameters) {
            typeAlias.typeParameters = node.typeParameters.map(tp => ({
                name: tp.name.text,
                constraint: tp.constraint ? this.getTypeText(tp.constraint) : null
            }));
        }

        result.types.push(typeAlias);
    }

    /**
     * Extract import declaration
     */
    extractImport(node, result) {
        const imp = {
            kind: 'import',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            moduleSpecifier: null,
            namedImports: [],
            defaultImport: null,
            namespaceImport: null,
            isTypeOnly: !!node.importClause?.isTypeOnly
        };

        if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
            imp.moduleSpecifier = node.moduleSpecifier.text;
        }

        if (node.importClause) {
            // Default import
            if (node.importClause.name) {
                imp.defaultImport = node.importClause.name.text;
            }

            // Named bindings
            if (node.importClause.namedBindings) {
                if (ts.isNamespaceImport(node.importClause.namedBindings)) {
                    imp.namespaceImport = node.importClause.namedBindings.name.text;
                } else if (ts.isNamedImports(node.importClause.namedBindings)) {
                    imp.namedImports = node.importClause.namedBindings.elements.map(elem => ({
                        name: elem.name.text,
                        propertyName: elem.propertyName ? elem.propertyName.text : null,
                        isTypeOnly: !!elem.isTypeOnly
                    }));
                }
            }
        }

        result.imports.push(imp);
    }

    /**
     * Extract export declaration
     */
    extractExport(node, result) {
        const exp = {
            kind: 'export',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            moduleSpecifier: null,
            namedExports: [],
            isDefault: false,
            exportClause: null
        };

        if (node.kind === ts.SyntaxKind.ExportAssignment) {
            exp.isDefault = !!node.isExportEquals === false;
            exp.expression = this.getNodeText(node.expression);
        } else if (node.kind === ts.SyntaxKind.ExportDeclaration) {
            if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
                exp.moduleSpecifier = node.moduleSpecifier.text;
            }

            if (node.exportClause) {
                if (ts.isNamedExports(node.exportClause)) {
                    exp.namedExports = node.exportClause.elements.map(elem => ({
                        name: elem.name.text,
                        propertyName: elem.propertyName ? elem.propertyName.text : null
                    }));
                } else if (ts.isNamespaceExport(node.exportClause)) {
                    exp.namespaceExport = node.exportClause.name.text;
                }
            }
        }

        result.exports.push(exp);
    }

    /**
     * Extract variable declaration
     */
    extractVariable(node, result) {
        const variable = {
            name: node.name.text,
            kind: 'variable',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            type: node.type ? this.getTypeText(node.type) : null,
            initializer: node.initializer ? this.getNodeText(node.initializer) : null,
            isConst: false,
            isLet: false,
            isVar: false
        };

        // Determine variable declaration kind from parent
        const parent = node.parent;
        if (parent && parent.kind === ts.SyntaxKind.VariableDeclarationList) {
            const flags = parent.flags;
            variable.isConst = !!(flags & ts.NodeFlags.Const);
            variable.isLet = !!(flags & ts.NodeFlags.Let);
            variable.isVar = !variable.isConst && !variable.isLet;
        }

        result.variables.push(variable);
    }

    /**
     * Extract call expression
     */
    extractCallExpression(node, result) {
        const call = {
            kind: 'call',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            expression: this.getNodeText(node.expression),
            arguments: [],
            typeArguments: []
        };

        // Extract arguments
        if (node.arguments) {
            call.arguments = node.arguments.map(arg => ({
                text: this.getNodeText(arg),
                kind: ts.SyntaxKind[arg.kind]
            }));
        }

        // Extract type arguments
        if (node.typeArguments) {
            call.typeArguments = node.typeArguments.map(ta => this.getTypeText(ta));
        }

        result.callExpressions.push(call);
    }

    /**
     * Extract property access expression
     */
    extractPropertyAccess(node, result) {
        const access = {
            kind: 'property_access',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            expression: this.getNodeText(node.expression),
            name: node.name.text,
            full: this.getNodeText(node)
        };

        result.propertyAccess.push(access);
    }

    // Helper methods

    getLineNumber(node) {
        return node.getSourceFile ? 
            node.getSourceFile().getLineAndCharacterOfPosition(node.getStart()).line + 1 :
            1;
    }

    getColumnNumber(node) {
        return node.getSourceFile ?
            node.getSourceFile().getLineAndCharacterOfPosition(node.getStart()).character + 1 :
            1;
    }

    getNodeText(node) {
        return node.getSourceFile ? 
            node.getSourceFile().text.substring(node.getStart(), node.getEnd()) :
            '<text unavailable>';
    }

    getTypeText(type) {
        return type.getSourceFile ?
            type.getSourceFile().text.substring(type.getStart(), type.getEnd()) :
            '<type unavailable>';
    }

    getPropertyName(name) {
        if (ts.isIdentifier(name)) {
            return name.text;
        } else if (ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
            return name.text;
        } else if (ts.isComputedPropertyName(name)) {
            return `[${this.getNodeText(name.expression)}]`;
        }
        return '<computed>';
    }

    hasModifier(node, kind) {
        return node.modifiers && node.modifiers.some(mod => mod.kind === kind);
    }

    isExported(node) {
        return this.hasModifier(node, ts.SyntaxKind.ExportKeyword) ||
               this.hasModifier(node, ts.SyntaxKind.DefaultKeyword);
    }

    getJSDocComment(node) {
        if (node.jsDoc && node.jsDoc.length > 0) {
            return node.jsDoc[0].comment || '';
        }
        return null;
    }

    extractMethodForClass(node) {
        return {
            name: this.getPropertyName(node.name),
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: node.parameters ? node.parameters.map(p => ({
                name: p.name.text,
                type: p.type ? this.getTypeText(p.type) : 'any'
            })) : [],
            returnType: node.type ? this.getTypeText(node.type) : null,
            isStatic: this.hasModifier(node, ts.SyntaxKind.StaticKeyword),
            isPrivate: this.hasModifier(node, ts.SyntaxKind.PrivateKeyword),
            isProtected: this.hasModifier(node, ts.SyntaxKind.ProtectedKeyword),
            isAbstract: this.hasModifier(node, ts.SyntaxKind.AbstractKeyword),
            isAsync: this.hasModifier(node, ts.SyntaxKind.AsyncKeyword)
        };
    }

    extractProperty(node) {
        return {
            name: this.getPropertyName(node.name),
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            type: node.type ? this.getTypeText(node.type) : null,
            initializer: node.initializer ? this.getNodeText(node.initializer) : null,
            isStatic: this.hasModifier(node, ts.SyntaxKind.StaticKeyword),
            isPrivate: this.hasModifier(node, ts.SyntaxKind.PrivateKeyword),
            isProtected: this.hasModifier(node, ts.SyntaxKind.ProtectedKeyword),
            isReadonly: this.hasModifier(node, ts.SyntaxKind.ReadonlyKeyword),
            isOptional: !!node.questionToken
        };
    }

    extractConstructor(node) {
        return {
            name: 'constructor',
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: node.parameters ? node.parameters.map(p => ({
                name: p.name.text,
                type: p.type ? this.getTypeText(p.type) : 'any',
                isPrivate: this.hasModifier(p, ts.SyntaxKind.PrivateKeyword),
                isProtected: this.hasModifier(p, ts.SyntaxKind.ProtectedKeyword),
                isPublic: this.hasModifier(p, ts.SyntaxKind.PublicKeyword),
                isReadonly: this.hasModifier(p, ts.SyntaxKind.ReadonlyKeyword)
            })) : []
        };
    }

    extractPropertySignature(node) {
        return {
            name: this.getPropertyName(node.name),
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            type: node.type ? this.getTypeText(node.type) : null,
            isOptional: !!node.questionToken,
            isReadonly: this.hasModifier(node, ts.SyntaxKind.ReadonlyKeyword)
        };
    }

    extractMethodSignature(node) {
        return {
            name: this.getPropertyName(node.name),
            line: this.getLineNumber(node),
            column: this.getColumnNumber(node),
            parameters: node.parameters ? node.parameters.map(p => ({
                name: p.name.text,
                type: p.type ? this.getTypeText(p.type) : 'any',
                optional: !!p.questionToken
            })) : [],
            returnType: node.type ? this.getTypeText(node.type) : null,
            isOptional: !!node.questionToken
        };
    }
}

// Main execution logic
if (require.main === module) {
    const bridge = new TypeScriptBridge();
    
    // Handle command line arguments
    const args = process.argv.slice(2);
    if (args.length === 0) {
        console.error('Usage: node ts_bridge.js <command> [args...]');
        process.exit(1);
    }

    const command = args[0];
    
    try {
        switch (command) {
            case 'parse':
                if (args.length < 2) {
                    console.error('Usage: node ts_bridge.js parse <file_path> [tsconfig_path]');
                    process.exit(1);
                }
                
                const filePath = args[1];
                const tsConfigPath = args[2];
                
                const initResult = bridge.initialize([filePath], tsConfigPath);
                if (!initResult.success) {
                    console.error(JSON.stringify({ success: false, error: initResult.error }));
                    process.exit(1);
                }
                
                const result = bridge.parseFile(filePath);
                console.log(JSON.stringify(result, null, 2));
                break;
                
            default:
                console.error(`Unknown command: ${command}`);
                process.exit(1);
        }
    } catch (error) {
        console.error(JSON.stringify({ success: false, error: error.message }));
        process.exit(1);
    }
}

module.exports = TypeScriptBridge;